socket通信
socket
socket 被译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。
通过 socket 约定,不同计算机之间可以进行通信
在linux里一切皆文件,socket是一个表示网络连接的文件,对应网络连接的文件描述符。
在windows里,socket是一个网络连接,有别于文件。
流格式套接字
流格式套接字也叫“面向连接的套接字”(Stream Sockets),在代码中使用 SOCK_STREAM 表示。
SOCK_STREAM 是一种可靠的、双向的通信数据流,
数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送。
流格式套接字使用了 TCP 协议,TCP 协议会控制你的数据按照顺序到达并且没有错误。
TCP
TCP全称The Transmission Control Protocol,传输控制协议
各个标志位及其意义:SYN(建立连接标志位)、ACK(确认标志位)、FIN(释放连接标志位)
TCP建立客户端与服务端的连接需要经过三次握手
第一次:Client将SYN置1,并产生随机数seq=x,发送数据包,Server接受判断SYN为1
第二次:Server将SYN和ACK置1,ack=x+1,seq=y,发送数据包,Client判断ack为x+1,ACK为1
第三次:Client将ACK置1,ack=y+1,发送数据包,Server接受判断ack为y+1,ACK为1
上述过程完成即建立连接
TCP断开客户端与服务端的连接需要经过四次挥手
第一次挥手:Client将FIN置1,发送数据包
第二次挥手:Server接受判断FIN为1,将ACK置1,发送数据包
第三次挥手:Server做好了释放服连接准备,将FIN置1,发送数据包
第四次挥手:Client接受判断FIN为1,将ACK置1,发送数据包
上述过程完成即断开连接
代码实现
代码实现分为客户端(client)和服务端(server)的实现
C++实现
C语言中文网的例程非常清晰,可以直接参考使用
Windows参考:http://c.biancheng.net/view/2129.html
Linux参考:http://c.biancheng.net/view/2128.html
注意事项:
在windows下使用g++编译需要加上-lws2_32选项,例如编译server.cpp如下
g++ server.cpp -o server.exe -lws2_32
如果用该代码与调试助手通信,需要注意端口和地址格式
例如我拿sockit进行调试,地址格式为AF_INET,端口前需要加上htons
Qt实现
Qt支持跨平台,我在windows上实现的效果,可以运用到linux。
最终实现了client界面和server界面编程,并经测试两端可以互通。
client界面
#include "clientwindow.h"
#include "serverwindow.h"
ClientWindow::ClientWindow(QWidget *parent)
: QMainWindow(parent)
{
ChooseClientButton = new QPushButton(this);
ChooseClientButton->setText("客户端");
ChooseClientButton->move(0,0);
ChooseServerButton = new QPushButton(this);
ChooseServerButton->setText("服务端");
ChooseServerButton->move(100,0);
connect(ChooseServerButton,SIGNAL(clicked()),this,SLOT(ChooseServerButtonClicked()));
IpLabel = new QLabel(this);
IpLabel->setText("Ip:");
IpLabel->move(70,50);
IpInput = new QLineEdit(this);
IpInput->setFixedSize(200,35);
IpInput->move(100,50);
IpInput->setPlaceholderText("Ip地址...");
IpInput->setClearButtonEnabled(true);
PortLabel = new QLabel(this);
PortLabel->setText("Port:");
PortLabel->move(50,100);
PortInput = new QLineEdit(this);
PortInput->setFixedSize(200,35);
PortInput->move(100,100);
PortInput->setPlaceholderText("端口...");
PortInput->setClearButtonEnabled(true);
ConnectButton = new QPushButton(this);
ConnectButton->setText("连接");
ConnectButton->move(50,150);
connect(ConnectButton,SIGNAL(clicked()),this,SLOT(ConnectButtonClicked()));
connected = false;
DisconnectButton = new QPushButton(this);
DisconnectButton->setText("断开");
DisconnectButton->move(190,150);
connect(DisconnectButton,SIGNAL(clicked()),this,SLOT(DisconnectButtonClicked()));
TextInput = new QTextEdit(this);
TextInput->move(50,230);
TextInput->setFixedSize(250,300);
TextInputLabel = new QLabel(this);
TextInputLabel->move(50,200);
TextInputLabel->setText("发送内容");
SendButton = new QPushButton(this);
SendButton->move(50,550);
SendButton->setText("发送");
SendButton->setFixedSize(250,35);
connect(SendButton,SIGNAL(clicked()),this,SLOT(SendButtonClicked()));
RecordLabel = new QLabel(this);
RecordLabel->move(550,50);
RecordLabel->setText("发送记录");
RecordText = new QTextEdit(this);
RecordText->move(350,80);
RecordText->setFixedSize(500,450);
RecordText->setReadOnly(true);
AllRecordText = QString();
socket = new QTcpSocket();
ClearRecordTextButton = new QPushButton(this);
ClearRecordTextButton->move(350,550);
ClearRecordTextButton->setText("清空");
ClearRecordTextButton->setFixedSize(500,35);
connect(ClearRecordTextButton,SIGNAL(clicked()),this,SLOT(ClearRecordTextButtonClicked()));
}
ClientWindow::~ClientWindow()
{
}
void ClientWindow::ConnectButtonClicked()
{
Ip = IpInput->text();
Port = PortInput->text().toInt();
socket->connectToHost(Ip, Port);
if(socket->waitForConnected(1000))
{
connected = true;
AllRecordText += "已连接\n";
IpInput->setReadOnly(true);
PortInput->setReadOnly(true);
connect(socket,SIGNAL(readyRead()),this,SLOT(ReadServerData()));
}
else
{
AllRecordText += "连接失败,请重新连接\n";
}
RecordText->setText(AllRecordText);
}
void ClientWindow::DisconnectButtonClicked()
{
connected = false;
socket->disconnectFromHost();
IpInput->setReadOnly(false);
PortInput->setReadOnly(false);
}
void ClientWindow::SendButtonClicked()
{
if(connected)
{
QStringList TextInputList = \
TextInput->toPlainText().split(QRegExp("[\r\n]"),QString::SkipEmptyParts);
for(int i =0;i<TextInputList.size();i++)
{
CurrentTime = QDateTime::currentDateTime();
QString current_date =CurrentTime.toString("hh:mm:ss");
TextInputList[i] +='\n';
AllRecordText += "[" + current_date + "] " + \
Ip + ":" + QString::number(Port) + ": " + \
"-->: " + TextInputList[i];
socket->write(TextInputList[i].toUtf8());
}
RecordText->setText(AllRecordText);
}
else
{
AllRecordText += "未连接,请连接\n";
RecordText->setText(AllRecordText);
}
}
void ClientWindow::ClearRecordTextButtonClicked()
{
AllRecordText = QString();
RecordText->setText(AllRecordText);
}
void ClientWindow::ChooseServerButtonClicked()
{
ServerWindow *server_window = new ServerWindow;
server_window->resize(900,600);
server_window->setFont(QFont("宋体",8));
server_window->setWindowTitle("Socket TCP 通信");
this->deleteLater();
this->close();
server_window->move(this->pos().x(),this->pos().y());
server_window->show();
}
void ClientWindow::ReadServerData()
{
QString ReadData = socket->readAll();
QStringList TextInputList = \
ReadData.split(QRegExp("[\r\n]"),QString::SkipEmptyParts);
for(int i =0;i<TextInputList.size();i++)
{
CurrentTime = QDateTime::currentDateTime();
QString current_date =CurrentTime.toString("hh:mm:ss");
TextInputList[i] +='\n';
AllRecordText += "[" + current_date + "] " + \
Ip + ":" + QString::number(Port) + ": " + \
"<--: " + TextInputList[i];
}
RecordText->setText(AllRecordText);
}
效果如下:
server界面
server和client实现类似
#include "serverwindow.h"
#include "clientwindow.h"
ServerWindow::ServerWindow(QWidget *parent)
: QMainWindow(parent)
{
ChooseClientButton = new QPushButton(this);
ChooseClientButton->setText("客户端");
ChooseClientButton->move(0,0);
ChooseServerButton = new QPushButton(this);
ChooseServerButton->setText("服务端");
ChooseServerButton->move(100,0);
connect(ChooseClientButton,SIGNAL(clicked()),this,SLOT(ChooseClientButtonClicked()));
IpLabel = new QLabel(this);
IpLabel->setText("Ip:");
IpLabel->move(70,50);
IpInput = new QLineEdit(this);
IpInput->setFixedSize(200,35);
IpInput->move(100,50);
IpInput->setPlaceholderText("Ip地址...");
IpInput->setClearButtonEnabled(true);
PortLabel = new QLabel(this);
PortLabel->setText("Port:");
PortLabel->move(50,100);
PortInput = new QLineEdit(this);
PortInput->setFixedSize(200,35);
PortInput->move(100,100);
PortInput->setPlaceholderText("端口...");
PortInput->setClearButtonEnabled(true);
ListenButton = new QPushButton(this);
ListenButton->setText("侦听");
ListenButton->move(50,150);
connect(ListenButton,SIGNAL(clicked()),this,SLOT(ListenButtonClicked()));
listened = false;
connected = false;
DisListenButton = new QPushButton(this);
DisListenButton->setText("取消侦听");
DisListenButton->move(190,150);
connect(DisListenButton,SIGNAL(clicked()),this,SLOT(DisListenButtonClicked()));
TextInput = new QTextEdit(this);
TextInput->move(50,230);
TextInput->setFixedSize(250,300);
TextInputLabel = new QLabel(this);
TextInputLabel->move(50,200);
TextInputLabel->setText("发送内容");
SendButton = new QPushButton(this);
SendButton->move(50,550);
SendButton->setText("发送");
SendButton->setFixedSize(250,35);
connect(SendButton,SIGNAL(clicked()),this,SLOT(SendButtonClicked()));
RecordLabel = new QLabel(this);
RecordLabel->move(550,50);
RecordLabel->setText("发送记录");
RecordText = new QTextEdit(this);
RecordText->move(350,80);
RecordText->setFixedSize(500,450);
RecordText->setReadOnly(true);
AllRecordText = QString();
ClearRecordTextButton = new QPushButton(this);
ClearRecordTextButton->move(350,550);
ClearRecordTextButton->setText("清空");
ClearRecordTextButton->setFixedSize(500,35);
connect(ClearRecordTextButton,SIGNAL(clicked()),
this,SLOT(ClearRecordTextButtonClicked()));
}
ServerWindow::~ServerWindow()
{
}
void ServerWindow::ListenButtonClicked()
{
Ip = IpInput->text();
Port = PortInput->text().toInt();
server = new QTcpServer(this);
server->listen(QHostAddress(Ip),Port);
connect(server,SIGNAL(newConnection()),this,SLOT(NewClientConnect()));
listened = true;
AllRecordText += "侦听中...\n";
ListenButton->setEnabled(false);
IpInput->setReadOnly(true);
PortInput->setReadOnly(true);
RecordText->setText(AllRecordText);
}
void ServerWindow::ClearRecordTextButtonClicked()
{
AllRecordText = QString();
RecordText->setText(AllRecordText);
}
void ServerWindow::ChooseClientButtonClicked()
{
ClientWindow *client_window = new ClientWindow;
client_window->resize(900,600);
client_window->setFont(QFont("宋体",8));
client_window->setWindowTitle("Socket TCP 通信");
this->deleteLater();
this->close();
client_window->move(this->pos().x(),this->pos().y());
client_window->show();
}
void ServerWindow::NewClientConnect()
{
AllRecordText += "已连接\n";
RecordText->setText(AllRecordText);
connected = true;
socket = new QTcpSocket(this);
socket = server->nextPendingConnection();
connect(socket,SIGNAL(readyRead()),this,SLOT(ReadClientData()));
}
void ServerWindow::ReadClientData()
{
QString ReadData = socket->readAll();
QStringList TextInputList = ReadData.split(QRegExp("[\r\n]"),QString::SkipEmptyParts);
for(int i =0;i<TextInputList.size();i++)
{
CurrentTime = QDateTime::currentDateTime();
QString current_date =CurrentTime.toString("hh:mm:ss");
TextInputList[i] +='\n';
AllRecordText += "[" + current_date + "] " + \
Ip + ":" + QString::number(Port) + ": " + \
"<--: " + TextInputList[i];
}
RecordText->setText(AllRecordText);
}
void ServerWindow::DisListenButtonClicked()
{
if(listened)
{
AllRecordText += "取消侦听\n";
RecordText->setText(AllRecordText);
server->deleteLater();
ListenButton->setEnabled(true);
IpInput->setReadOnly(false);
PortInput->setReadOnly(false);
listened = false;
connected = false;
}
}
void ServerWindow::SendButtonClicked()
{
if(connected)
{
QStringList TextInputList = TextInput->toPlainText().split(
QRegExp("[\r\n]"),QString::SkipEmptyParts);
for(int i =0;i<TextInputList.size();i++)
{
CurrentTime = QDateTime::currentDateTime();
QString current_date =CurrentTime.toString("hh:mm:ss");
TextInputList[i] +='\n';
AllRecordText += "[" + current_date + "] " + \
Ip + ":" + QString::number(Port) + ": " + \
"-->: " + TextInputList[i];
socket->write(TextInputList[i].toUtf8());
}
RecordText->setText(AllRecordText);
}
else
{
AllRecordText += "未连接,请连接\n";
RecordText->setText(AllRecordText);
}
}
效果如下:
程序打包
为了让程序可以在多个平台上使用
我将qt程序分别在windows和linux编译成可执行文件,并打包依赖环境
windows打包环境采用qt自带的windeployqt,比较简单
linux打包环境采用需要自己编写脚本文件,实现如下:
1.打包依赖项
编写了一个pack.sh用于打包依赖环境,内容如下
#!/bin/sh
dir=$(pwd)
liblist=$(ldd $1 | awk '{if (match($3,"/")){ printf("%s ",$3)}}')
cp $liblist $dir
通过ldd命令显示依赖环境,结合awk,match匹配获得动态链接库的绝对路径
最后通过cp命令将这些文件拷贝到当前文件夹、
2.运行时指定动态库链接路径
如果更换了一台电脑,它并不会默认当前文件夹为动态链接库搜索路径
所以需要一个与可执行文件同名的的脚本,内容如下
#!/bin/sh
appname=$(basename $0 | cut -d . -f1)
dirname=$(dirname $0)
LD_LIBRARY_PATH=$dirname
export LD_LIBRARY_PATH
chmod +x $dirname/$appname
$dirname/$appname
程序实现的也就是设置动态链接库为该目录
所以在其他机器上只要运行这个socket.sh即可将可执行文件链接该文件夹的动态链接库并成功执行
3.打包成App
为了获得更加方便的可点击执行的可执行文件,我还编写了getDesktop.sh脚本,内容如下
exe="socketTCP"
file_name=$exe".desktop"
echo "#!/usr/bin/env xdg-open" > $file_name
echo "[Desktop Entry]" >> $file_name
echo "Version=1.0" >> $file_name
echo "Type=Application" >> $file_name
echo "Terminal=false" >> $file_name
echo "Exec="$(pwd)"/"$exe".sh" >> $file_name
echo "Name="$exe >> $file_name
chmod +x $file_name
实现原理就是根据所在路径编写可点击执行的.desktop文件